let configuration = require('../configuration');
let Component = require('./Component');

class ComponentContainer {
    constructor() {
        this.components = [];

        this.addNumberCount = 0;

        this.mouse = {
            x: 0,
            y: 0,
            lastMouseDown: -1,
            activeComponent: null,
            lastClickAt: {
                x: 0,
                y: 0
            },
            lastClickAtActiveComponent: {
                x: 0,
                y: 0
            }
        };

        this.outstandComps = new Set([]);
    }

    init(initConfig) { // { game: ..., baseX: int, baseY: int, canvas, componentContainer[optional] }
        this.game = initConfig.game;
        this.canvas = initConfig.canvas;

        this.baseX = initConfig.baseX;
        this.baseY = initConfig.baseY;

        this.mouseHoldDelay = configuration.get('mouse.lastMouseDown.delay');
        this.parentComponentContainer = initConfig.componentContainer;
    }

    addComponent(comp, id, zIndex) {
        zIndex = zIndex || 0;
        if (!(comp instanceof Component)) {
            comp = new Component(comp, id, zIndex);
        }
        comp.init({
            game: this.game,
            componentContainer: this,
            canvas: this.canvas
        });
        comp.addNumber = (this.addNumberCount++ );
        this.components.push(comp);
        this.components = this.components.sort((a, b) => {
            if (a.zIndex == b.zIndex) return a.addNumber - b.addNumber;
            return a.zIndex - b.zIndex
        });
        this.game.broadcast('componentAdded', this, comp.o);
    }

    removeAllComponents() {
        for (let c in this.components) {
            this.game.broadcast('componentRemoved', this, c.o);
        }
        this.components = [];
    }

    setZIndex(comp, zIndex) {
        zIndex = zIndex || 0;
        for (let c of this.components) {
            if (c === comp || c.o === comp) {
                c.zIndex = zIndex;
            }
        }
        this.components = this.components.sort((a, b) => {
            if (a.zIndex == b.zIndex) return a.addNumber - b.addNumber;
            return a.zIndex - b.zIndex
        });
    }

    removeComponent(o) {
        for (let i = 0; i < this.components.length; ++i) {
            let c = this.components[i];
            if (c == o || c.o == o) {
                this.components.splice(i, 1);
                this.cancelOutstand(c);
                this.cancelOutstand(c.o);
                this.game.broadcast('componentRemoved', this, o);
                break;
            }
        }
    }

    mouseMove(x, y) {
        // translate x, y
        x -= this.baseX;
        y -= this.baseY;

        this.mouse.x = x;
        this.mouse.y = y;

        // mouse in and mouse out
        for (let c of this.components) {
            if (c.includesPoint(x, y)) {
                c.mouseIn();
                c.mousePos(x, y);
            } else {
                c.mouseOut();
            }
        }

        // move the component
        if (this.mouse.lastMouseDown > 0 && this.mouse.activeComponent) {
            if (Date.now() - this.mouse.lastMouseDown > this.mouseHoldDelay) {
                // is holding and moving
                this.mouse.activeComponent.move(x, y);
            } // else: mouse move by mistake, simply do nothing
            return;
        }

        let noActive = true;
        for (let i = this.components.length - 1; i >= 0; --i) {
            let c = this.components[i];
            if (noActive && c.includesPoint(x, y)) {
                // found one active
                if (c != this.mouse.activeComponent) {
                    if (this.mouse.activeComponent) {
                        this.mouse.activeComponent.inactive();
                    }
                    this.mouse.activeComponent = c;
                    c.active();
                } // else: already active and recorded, do nothing
                noActive = false;
            }
        }
        if (noActive) {
            if (this.mouse.activeComponent) {
                this.mouse.activeComponent.inactive();
                this.mouse.activeComponent = null;
            }
        }
    }

    mouseDown(x, y) {
        this.mouse.lastMouseDown = Date.now();
        this.mouse.lastClickAt.x = this.mouse.x;
        this.mouse.lastClickAt.y = this.mouse.y;
        if (this.mouse.activeComponent) {
            if (this.mouse.activeComponent.includesPoint(x, y)) {
                this.mouse.activeComponent.mouseDown(this.mouse.x, this.mouse.y);
            }
        }
    }

    mouseUp() {
        this.mouse.lastMouseDown = -1;
        if (this.mouse.activeComponent) {
            this.mouse.activeComponent.mouseUp();
        }
    }

    componentBroadcast(route, sender, msg) {
        for (let c of this.components) {
            c.on(route, sender, msg);
        }
    }

    draw(canvas) {
        let ctx = canvas.getContext('2d');
        ctx.save();
        ctx.translate(this.baseX, this.baseY);
        ctx.save();
        let outstandToDraw = [];
        this.components.forEach(c => {
            if (this.outstandComps.has(c) || this.outstandComps.has(c.o)) {
                outstandToDraw.push(c);
            } else {
                c.draw(canvas);
            }
        });
        if (outstandToDraw.length > 0) {
            ctx.save();
            ctx.fillStyle = 'rgba(0,0,0,0.5)';
            ctx.save();
            if (this.outstandConf) {
                ctx.fillRect(this.outstandConf.x, this.outstandConf.y, this.outstandConf.width, this.outstandConf.height);
            } else {
                let windowWidth = configuration.get('window.width');
                let windowHeight = configuration.get('window.height');
                ctx.fillRect(0, 0, windowWidth, windowHeight);
            }
            outstandToDraw.forEach(c => c.draw(canvas));
            ctx.restore();
            ctx.restore();
        }
        ctx.restore();
        ctx.restore();
    }

    setOutstandRect(x, y, width, height) {
        if (arguments.length == 1 && !arguments[0]) {
            this.outstandConf = null;
            return;
        }
        this.outstandConf = {
            x: x,
            y: y,
            width: width,
            height: height
        };
    }

    outstand(c) {
        this.outstandComps.add(c);
    }

    cancelOutstand(c) {
        this.outstandComps.delete(c);
    }
}

module.exports = ComponentContainer;
